package com.simpou.commons.utils.reflection;

import com.simpou.commons.utils.validation.Assertions;


/**
 * Classe utilitária para realização de conversões de tipos ("casts"). Use para
 * evitar "warnings" mesmo em locais onde a segurança do "cast" é garantida. Use
 * também para evitar NullPointerExceptions em "casts" envolvendo objetos nulos.
 *
 * @author Jonas Pereira
 * @since 2012-07-13
 * @version 2012-07-27
 */
@SuppressWarnings("unchecked")
public class Casts {
    /**
     * Realiza um cast com base no tipo de retorno esperado do chamador do
     * método.
     *
     * @param object Objeto a ser convertido.
     * @return Objeto convertido para o tipo de retorno esperado ou null se
     * objeto for null.
     */
    public static <T> T simpleCast(Object object) {
        return (object == null) ? null : (T) object;
    }

    /**
     * Evita chamadas inexatas quando um método espera um parâmetro do tipo
     * object varArgs (Object... param) e recebe um array de um tipo qualquer.
     *
     * @param array Array.
     * @return Array no formato varArgs.
     */
    public static <T> Object[] varArgs(final T[] array) {
        return array;
    }

    /**
     * Converte uma classe de um tipo qualquer para um tipo específico.
     *
     * <b> Use este método com muito cuidado! Checagem de tipo não pode ser
     * realizada neste caso. </b>
     *
     * @param target Classe objetivo.
     * @param source Classe a ser convertida.
     * @return Classe objetivo ou null caso source seja null.
     */
    public static <T> Class<T> rawClassCast(final Class<T> target,
        final Class<?> source) {
        if (source == null) {
            return null;
        }

        Assertions.notNull(target);

        // não é possível checar segurança disto !!
        return (Class<T>) source;
    }

    /**
     * Converte um tipo qualquer para um tipo de classe específico.
     *
     * <b> Use este método com muito cuidado! Checagem de tipo não pode ser
     * realizada neste caso. </b>
     *
     * @param target Tipo objetivo. É usado para identificar o tipo da classe de
     * retorno.
     * @param source Classe a ser convertida.
     * @return Classe objetivo ou null caso source seja null.
     */
    public static <T> Class<T> objClassCast(final T target,
        final Class<?> source) {
        if (source == null) {
            return null;
        }

        Assertions.notNull(target);

        // não é possível checar segurança disto !!
        return (Class<T>) source;
    }

    /**
     * Converte a classe superior de um tipo para uma classe cujo tipo é
     * inferior ao primeiro na hierarquia de classes.
     *
     * @param subClass Classe filha em qualquer nível.
     * @param superClass Classe pai.
     * @return Classe filha ou null se superClass for null.
     */
    public static <E, T extends E> Class<T> subClassCast(
        final Class<T> subClass, final Class<E> superClass) {
        if (superClass == null) {
            return null;
        }

        // subClass não pode ser null, se for gera ambiguidade e erro de
        // compilação
        return (Class<T>) superClass;
    }

    /**
     * Converte um objeto qualquer para um tipo específico.
     *
     * @param target Classe objetivo.
     * @param source Objeto a ser convertido.
     * @return Objeto objetivo ou null se source for null.
     * @throws ClassCastException Se classes não forem compatíveis.
     */
    public static <T> T objCast(final Class<T> target, final Object source) {
        return objCast(target, source, false);
    }

    /**
     * Obtém a classe genérica parametrizada de um objeto. O compilador não é
     * capaz de determinar a classe de um tipo genérico em tempo de execução,
     * então é necessário realizar um raw cast explícito.
     *
     * @param obj Objeto genérico e mtempo de execução.
     * @return Classe do objeto parametrizada com seu tipo.
     */
    public static <T> Class<T> getClass(final T obj) {
        if (obj == null) {
            return null;
        }

        // isso é seguro
        return (Class<T>) obj.getClass();
    }

    /**
     * Converte um objeto qualquer para um tipo específico.
     *
     * @param target Classe objetivo.
     * @param source Objeto a ser convertido.
     * @param ignoreAll Ignora todas validações e tenta fazer forçar a
     * conversão. Recurso perigoso!
     * @return Objeto objetivo ou null se source for null.
     * @throws ClassCastException Se classes não forem compatíveis.
     */
    public static <T> T objCast(final Class<T> target, final Object source,
        final boolean ignoreAll) {
        if (source == null) {
            return null;
        }

        Assertions.notNull(target);

        final Class<?> srcClass = source.getClass();

        if (ignoreAll || srcClass.isAssignableFrom(target)) {
            return (T) source;
        } else {
            throw new ClassCastException("Source " + srcClass.getName() +
                " is not compatible with target " + target.getName());
        }
    }

    /**
     * Converte um objeto superior de um tipo para um outro cujo tipo é inferior
     * ao primeiro na hierarquia de classes.
     *
     * @param subClass Classe filha em qualquer nível.
     * @param superObj Objeto pai.
     * @return Objeto filho ou null se superObj for null.
     */
    public static <E, T extends E> T subObjCast(final Class<T> subClass,
        final E superObj) {
        if (superObj == null) {
            return null;
        }

        // subClass não pode ser null, se for gera ambiguidade e erro de
        // compilação
        return (T) superObj;
    }

    /**
     * @param enumType Classe do tipo enum.
     * @param name Nome do atributo do enum que deseja ser obtido.
     * @return Valor do enum.
     * @throws IllegalArgumentException Se tipo não for um enum.
     */
    public static Object valueOfEnum(final Class<?> enumType, final String name) {
        if (Enum.class.isAssignableFrom(enumType)) {
            return Enum.valueOf((Class<?extends Enum>) enumType, name);
        } else {
            throw new IllegalArgumentException("Type is not an enum.");
        }
    }
}
